package team.se.tms.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import team.se.tms.annotation.CheckFormat;
import team.se.tms.annotation.Number;
import team.se.tms.annotation.Regex;
import team.se.tms.annotation.SpecialCheck;
import team.se.tms.exception.ParamException;
import team.se.tms.util.FormatUtil;

import java.io.File;
import java.lang.reflect.*;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 检查标注了CheckFormat注解的方法参数
 *
 * @author johnny
 * @date 2018/11/21
 * @update on 2018/12/26 by johnny
 */
@Aspect
@Component
public class ParamCheckAspect {

    static List<String> classList= new ArrayList<>();
    static {
        String dtoPackage ="\\src\\main\\java\\team\\se\\tms\\dto";
        String poPackage ="\\src\\main\\java\\team\\se\\tms\\po";
        String relativelyPath=System.getProperty("user.dir");
        File dtoFile = new File(relativelyPath+dtoPackage);
        File poFile = new File(relativelyPath+poPackage);
        List<File> entityFiles = new ArrayList<>();
        entityFiles.addAll(Arrays.asList(dtoFile.listFiles()));
        entityFiles.addAll(Arrays.asList(poFile.listFiles()));
        for (File childFile : entityFiles) {
            String childFilePath = childFile.getPath();
            if (childFilePath.endsWith(".java")) {
                childFilePath = childFilePath.substring(childFilePath.lastIndexOf("\\")+1, childFilePath.lastIndexOf("."));
                classList.add(childFilePath);
            }
        }
    }

    /**
     * 拦截CheckFormat注解的方法
     *
     * @return Object 被拦截方法的执行结果
     * @throws Exception 异常
     * @create on 2018/11/21 by johnny
     */

    @Around("@annotation(checkFormat)")
    public Object parameterCheck(ProceedingJoinPoint joinPoint,CheckFormat checkFormat) throws Throwable{

        String methodName = joinPoint.getSignature().getName();
        FormatUtil formatUtil = new FormatUtil();
        System.out.println("正在检查"+methodName+"的参数");
        Object[] args = joinPoint.getArgs();
        System.out.println("参数为"+Arrays.asList(joinPoint.getArgs()));
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        //2.最关键的一步:通过这获取到方法的所有参数名称的字符串数组
        Parameter[] parameters = methodSignature.getMethod().getParameters();

        for(int i=0;i<args.length;i++){
            Parameter parameter= parameters[i];
            //有注解
            if(parameter.getAnnotations()!=null && parameter.getAnnotations().length!=0){
                //注解有@SpecialCheck的参数检查
                if(parameter.getAnnotation(SpecialCheck.class) != null){
                    doSpecialCheck(parameters[i],args[i]);
                    continue;
                }
                //注解有@Number的参数检查
                if(parameter.getAnnotation(Number.class) != null){
                    doNumberCheck(args[i],parameter.getAnnotation(Number.class));
                    continue;
                }
                //注解有@Regex的参数检查
                if(parameter.getAnnotation(Regex.class) != null){
                    doRegexCheck(args[i],parameter.getAnnotation(Regex.class));
                    continue;
                }
            }

            //将标注了不需要检查的参数转化为set
            Set<String> noCheckSet=new HashSet<String>(Arrays.asList(checkFormat.value()));
            //实体检测
            if(isEntityClass(parameter.getType())){
                //对实体类进行检测
                checkEntityFormat(parameter.getType(),args[i],noCheckSet);
            }
        }
        return joinPoint.proceed();
    }

    /**
     * 对SpecialCheck进行处理
     * 调用ForamatUtil中的 checkXXFormat方法进行参数检查
     *
     * @return Boolean 格式检查的结果
     * @throws Exception 异常
     * @create on 2018/11/21 by johnny
     */

    public void doSpecialCheck(Parameter parameter,Object obj) throws Exception{
        SpecialCheck specialCheck =parameter.getAnnotation(SpecialCheck.class);
        String type = specialCheck.value();
        Class<FormatUtil> clazz =FormatUtil.class;
        FormatUtil formatUtil = new FormatUtil();
        String methodName = "check" +type+"Format";
        Method method = clazz.getDeclaredMethod(methodName, parameter.getType());
        method.setAccessible(true);
        boolean result = (boolean) method.invoke(formatUtil,obj);
        if(!result){
            throw new ParamException();
        }
    }

    /**
     * 对@Number注解进行处理
     *
     * @param obj 要检查的参数
     * @return number @Number注解
     * @throws Exception 异常
     * @create on 2018/12/26 by johnny
     */
    public void doNumberCheck(Object obj,Number number) throws Exception{

        int param;
        if(obj.getClass().isAssignableFrom(java.lang.Float.class)){
            param = ((Float)obj).intValue();
        }else{
            param = (int)obj;
        }
        int min = number.min();
        int max = number.max();
        if(param<min || param>max){
            throw new ParamException();
        }
    }

    /**
     * 对@Regex注解进行处理
     *
     * @param obj 要检查的参数
     * @return regex @Regex注解
     * @throws Exception 异常
     * @create on 2018/12/26 by johnny
     */
    public void doRegexCheck(Object obj, Regex regex){
        String fieldValue = (String)obj;

        if ((fieldValue==null) || !Pattern.matches(regex.value(), fieldValue)) {
            //如果校验失败
            throw new ParamException();
        }
    }
    /**
     * 对List类型进行处理
     *
     * @param field 要检查的参数类型
     * @param arg 要检查的参数
     * @param noCheckSet 不需要检查的参数列表
     * @throws Exception 异常
     * @create on 2018/12/26 by johnny
     */
    public void checkListFormat(Field field,Object arg,Set<String> noCheckSet) throws Exception{
        Type genericType = field.getGenericType();
        // 如果是泛型参数的类型
        if(genericType instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) genericType;
            //得到泛型里的class类型对象
            Class<?> genericClazz = (Class<?>) pt.getActualTypeArguments()[0];
            if(isEntityClass(genericClazz)){
                List  genericList= (List)field.get(arg);
                if(genericList==null ||genericList.size()==0){
                    return;
                }
                for(int i=0;i<genericList.size();i++){
                    //对实体类进行检测
                    checkEntityFormat(genericClazz, genericList.get(i), noCheckSet);
                }
            }
        }
    }
    /**
     * 对实体类进行处理
     *
     * @param classzz 要检查的参数类型
     * @param arg 要检查的参数
     * @param noCheckSet 不需要检查的参数列表
     * @throws Exception 异常
     * @create on 2018/11/28 by johnny
     */
    public void checkEntityFormat(Class classzz,Object arg,Set<String> noCheckSet) throws Exception{

        if(arg == null){
            throw new ParamException();
        }

        Class<?> paramClazz = classzz;
        //得到参数的所有成员变量
        Field[] declaredFields = paramClazz.getDeclaredFields();
        for (Field field : declaredFields) {
            field.setAccessible(true);
            //放行checkFormat标注了不需要检查的参数
            if(noCheckSet!=null && noCheckSet.contains(field.getName())){
                continue;
            }
            //有注解
            if(field.getAnnotations()!=null && field.getAnnotations().length!=0){
                //注解有@Number的参数检查
                if(field.getAnnotation(Number.class) != null){
                    doNumberCheck(field.get(arg),field.getAnnotation(Number.class));
                    continue;
                }
                //注解有@Regex的参数检查
                if(field.getAnnotation(Regex.class) != null){
                    doRegexCheck(field.get(arg),field.getAnnotation(Regex.class));
                    continue;
                }
            }
            if(isEntityClass(field.getType())) {
                Object obj = field.get(arg);
                //对实体类进行检测
                checkEntityFormat(obj.getClass(), obj, noCheckSet);
                continue;
            }

            //list检测
            if(isListClass(field.getType())){
                Object obj = field.get(arg);
                //对list类进行检测
                checkListFormat(field,arg,noCheckSet);
                continue;
            }
        }
    }

    /**
     * 是不是指定的实体类
     *
     * @param classzz
     * @return boolean 检测结果
     * @throws Exception 异常
     * @create on 2018/12/26 by johnny
     */
    public boolean isEntityClass(Class classzz){
        String className = classzz.getName();
        String name = className.substring(className.lastIndexOf(".")+1);
        for(int i = 0;i<classList.size();i++){
            if(classList.get(i).equals(name)){
                return true;
            }
        }
        return false;
    }

    /**
     * 判断该类是不是list或list的实现类
     *
     * @param clazz 类类型
     * @return boolean 判断结果
     * @create on 2018/12/26 by johnny
     */
    public boolean isListClass(Class clazz){
        return List.class.isAssignableFrom(clazz);
    }
}
